import {
  Invitation,
  Inviter,
  Referral,
  Registerer,
  RegistererOptions,
  Session,
  SessionState,
  UserAgent,
  UserAgentOptions,
  UserAgentDelegate,
  Core,
  Web,
  RegistererRegisterOptions,
  InvitationAcceptOptions,
  UserAgentState
} from 'sip.js'

interface Options {
  server: string,
  sip: string,
  secret: string,
  onmessage?(event: string): void
  error?(error: string): void
}
// let registerer: any = {}
// let currentConfig: any = {}
// let Ginvitation: Invitation
export default class M7Webrtc {
  private config: Options | undefined
  private registerer: any
  private userAgent: any
  public message: ((arg0: any) => void | undefined) | undefined
  private Ginvitation: any
  constructor (options: any) {
    this.init(options)
  }

  private init (config: Options) {
    // 获取HTMLElement
    this.config = config
    const audioElement = getAudio('m7remoteAudio')
    // HTMLElement to 获取HTMLElement
    function getAudio (id: string): HTMLAudioElement {
      const el = document.getElementById(id)
      if (!(el instanceof HTMLAudioElement)) { // 没有音频dom,重新创建一个
        // throw new Error(`Element '${id}' not found or not an audio element.`)
        const audio = new Audio()
        audio.controls = true
        audio.autoplay = true
        audio.id = 'm7remoteAudio'
        audio.style.display = 'none'
        document.body.appendChild(audio)
        return document.getElementById(id) as HTMLAudioElement
      } else {
        return el
      }
    }
    // Assign a MediaStream to an HTMLMediaElement and update if tracks change.
    // 媒体流绑定标签
    function assignStream (stream: MediaStream, element: HTMLMediaElement): void {
      // Set element source.
      element.autoplay = true; // Safari does not allow calling .play() from a non user action
      element.srcObject = stream;

      // Load and start playback of media.
      element.play().catch((error: Error) => {
        console.error('Failed to play media');
        console.error(error);
      });

      // If a track is added, load and restart playback of media.
      stream.onaddtrack = (): void => {
        element.load(); // Safari does not work otheriwse
        element.play().catch((error: Error) => {
          console.error('Failed to play remote media on add track');
          console.error(error);
        });
      };

      // If a track is removed, load and restart playback of media.
      stream.onremovetrack = (): void => {
        element.load(); // Safari does not work otheriwse
        element.play().catch((error: Error) => {
          console.error('Failed to play remote media on remove track');
          console.error(error);
        });
      };
    }
    /*
     * Create a user agent
     * 创建用户代理
     */
    const transportOptions = {
      server: 'wss://' + config.server
    }

    const userAgentDelegate: any = {
      onInvite: (invitation: any): void => {
        console.log('userAgent invitation', invitation)
        this.Ginvitation = invitation
        // An Invitation is a Session
        const incomingSession: any = invitation
        // Setup incoming session delegate
        incomingSession.delegate = {
          // Handle incoming REFER request.
          onRefer (referral: any): void {
            console.log(referral)
          }
        };
        this.sendMessage('incomingcall')
        this.Ginvitation._onCancel = (message: any): void => {
          this.sendMessage('cancel')
        }
        // Handle incoming session state changes.
        incomingSession.stateChange.addListener((state: any) => {
          switch (state) {
            case SessionState.Initial:
              break;
            // 会话建立
            case SessionState.Establishing:
              break;
            // 会话已经建立
            case SessionState.Established:
              // eslint-disable-next-line no-case-declarations
              const sessionDescriptionHandler = incomingSession.sessionDescriptionHandler
              if (!sessionDescriptionHandler || !(sessionDescriptionHandler instanceof Web.SessionDescriptionHandler)) {
                throw new Error('Invalid session description handler.');
              }
              if (audioElement) {
                assignStream(sessionDescriptionHandler.remoteMediaStream, audioElement)
              }
              break;
            case SessionState.Terminating:
              break;
            case SessionState.Terminated:
              break;
            default:
              throw new Error('Unknown session state.');
          }
        })
        const constrainsDefault: MediaStreamConstraints = {
          audio: true,
          video: false
        }
        const options: any = {
          sessionDescriptionHandlerOptions: {
            constraints: constrainsDefault
          }
        }
        const calltype = this.Ginvitation.incomingInviteRequest.message.headers['X-Asterisk-Calltype'][0].raw
        if (calltype === 'dialout') {
          invitation.accept(options)
            .then(() => {
              console.log("接听成功");
            }).catch((error: Error) => {
              console.log("接听失败");
            })
        }
      },
      onConnect: () => {
        console.log('onConnect==============')
      },
      onDisconnect: () => {
        console.log('onDISCONNECT===============')
      },
      onRegisterRequest: () => {
        console.log('onRegisterRequest===============')
      },
      onRegister: () => {
        console.log('onRegister=============')
      },
      onMessage: (message: any) => {
        console.log(message)
        console.log('onMessage===============')
      }
    }

    const uri = UserAgent.makeURI('sip:' + config.sip + '@' + config.server);
    if (!uri) {
      throw new Error('Failed to create URI');
    }
    const userAgentOptions: any = {
      delegate: userAgentDelegate,
      displayName: 'webrtc',
      uri,
      transportOptions,
      authorizationUsername: config.sip,
      // authorizationPassword: config.secret,
      authorizationHa1: config.secret
      /* ... */
    }
    console.log(userAgentOptions, 'oiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii')
    this.userAgent = new UserAgent(userAgentOptions);
    const registererOptions: any = {
      expires: 10
    }
    this.registerer = new Registerer(this.userAgent, registererOptions);

    /***
     * 注册的状态
     */
    const requestDelegate: any = {
      onAccept: (response: any): void => {
        console.log('onAccept.....', response)
      },
      onProgress: (response: any): void => {
        console.log('onProgress.....', response)
      },
      onRedirect: (response: any): void => {
        console.log('onRedirect.....', response)
      },
      onReject: (response: any): void => {
        console.log('onReject.....', response)
      },
      onTrying: (response: any): void => {
        console.log('onTrying.....', response)
      }
    }

    const registererRegisterOptions: any = {
      requestDelegate
    }
    this.registerHandle(registererRegisterOptions)
  }

  private async registerHandle (registererRegisterOptions: any) {
    await (this.userAgent as any).start();
    // 注册
    await this.registerer.register(registererRegisterOptions);
  }

  public accept = async (): Promise<void> => {
    const constrainsDefault: MediaStreamConstraints = {
      audio: true,
      video: false
    }
    const options: any = {
      sessionDescriptionHandlerOptions: {
        constraints: constrainsDefault
      }
    }
    return (this.Ginvitation as any).accept(options)
  }

  public m7UnregisterWebrtc = async (): Promise<boolean | void> => { // 取消注册
    if (!this.registerer) {
      return false
    }
    return this.registerer.unregister({ all: true }).then((res: any) => {
      console.log('取消注册')
    }).catch((e: any) => {
      console.log(e)
    })
  }

  private sendMessage (message: string) {
    if (this.message) {
      this.message(message)
    }
  }

  public sendDTMF(tone: string): Promise<void> {

    // As RFC 6086 states, sending DTMF via INFO is not standardized...
    //
    // Companies have been using INFO messages in order to transport
    // Dual-Tone Multi-Frequency (DTMF) tones.  All mechanisms are
    // proprietary and have not been standardized.
    // https://tools.ietf.org/html/rfc6086#section-2
    //
    // It is however widely supported based on this draft:
    // https://tools.ietf.org/html/draft-kaplan-dispatch-info-dtmf-package-00

    // Validate tone
    if (!/^[0-9A-D#*,]$/.exec(tone)) {
      return Promise.reject(new Error("Invalid DTMF tone."));
    }

    if (!this.Ginvitation) {
      return Promise.reject(new Error("Session does not exist."));
    }

    // The UA MUST populate the "application/dtmf-relay" body, as defined
    // earlier, with the button pressed and the duration it was pressed
    // for.  Technically, this actually requires the INFO to be generated
    // when the user *releases* the button, however if the user has still
    // not released a button after 5 seconds, which is the maximum duration
    // supported by this mechanism, the UA should generate the INFO at that
    // time.
    // https://tools.ietf.org/html/draft-kaplan-dispatch-info-dtmf-package-00#section-5.3
    const dtmf = tone;
    const duration = 2000;
    const body = {
      contentDisposition: "render",
      contentType: "application/dtmf-relay",
      content: "Signal=" + dtmf + "\r\nDuration=" + duration
    };
    const requestOptions = { body };

    return this.Ginvitation.info({ requestOptions }).then(() => {
      return;
    });
  }

  public disconnect() {
    return (this.userAgent as any).stop()
  }
  
  public connect () {
    if ((this.userAgent as any).state !== UserAgentState.Started) {
      return (this.userAgent as any).start();
    }
    return (this.userAgent as any).reconnect();
  }

  public isConnected(): boolean {
    return (this.userAgent as any).isConnected();
  }
}
